package utils;

import java.io.File;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * MethodSignatureParser allows us to parse a small XML Document into a Document Object Model (DOM).
 * The DOM should represent method signatures offered by an XML RPC service.
 * @see MethodInput.xsd
 * @date Mar 16, 2011
 * @author Eric Kisner
 */
public class MethodSignatureParser {

    private static final String METHOD = "Method";
    private static final String METHOD_NAME = "methodName";
    private static final String RETURN_TYPE = "returnType";
    private static final String PARAM = "Param";
    private static final String PARAM_VALUE = "paramValue";
    private static final String PARAM_NAME = "paramname";
    private String fileName;
    private Document doc;

    /**
     * Initialize a new MethodSignatureParser
     * @param fileName Provide the XML document that we should load a DOM for.
     */
    public MethodSignatureParser( String fileName ) {
        this.fileName = fileName;
        initDOM();
    }

    /**
     * Initializes the DOM.
     */
    private void initDOM() {
        try {
            DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
            doc = builder.parse( new File( fileName ) );
            if( doc == null ) {
                throw new RuntimeException( "Unable to build Document Object Model using file " + fileName + "." );
            }
        } catch( Exception e ) {
            ExceptionUtils.handleException( e );
            throw new RuntimeException( e );
        }
    }

    /**
     * Get the root Element of the current DOM.
     */
    public Element getRootNode() {
        return doc.getDocumentElement();
    }

    /**
     * Returns a NodeList of all the Elements in document order with a given tag name and are contained in the
     * document.
     * @param tagname The name of the tag to match on.
     * @return A new NodeList object containing all the matched Elements.
     */
    public NodeList getChildren( String tagname ) {
        return doc.getElementsByTagName( tagname );
    }

    /**
     * Accepts a method name and returns the 'Method' node associated with it.
     * @param methodName The name of the method we are going after.
     * @return The method node with the matching method name, or null if there are none.
     */
    public Node getMethodNode( String methodName ) {
        NodeList methodNodes = getChildren( METHOD );
        for( int i = 0; i < methodNodes.getLength(); i++ ) {
            String mName = getValueOfElementInMethod( (Element) methodNodes.item( i ), METHOD_NAME );
            if( mName.equals( methodName ) ) {
                return methodNodes.item( i );
            }
        }
        return null;
    }

    /**
     * Returns a list of method names.
     */
    public List<String> getAllMethodNames() {
        NodeList methodNodes = getChildren( METHOD );
        String[] ret = new String[methodNodes.getLength()];
        for( int i = 0; i < methodNodes.getLength(); i++ ) {
            String mName = getValueOfElementInMethod( (Element) methodNodes.item( i ), METHOD_NAME );
            ret[i] = mName;
        }
        return Arrays.asList( ret );
    }

    /**
     * Returns a list of all method return types (same order as method names).
     */
    public List<String> getAllReturnTypes() {
        NodeList methodNodes = getChildren( METHOD );
        String[] ret = new String[methodNodes.getLength()];
        for( int i = 0; i < methodNodes.getLength(); i++ ) {
            String retType = getValueOfElementInMethod( (Element) methodNodes.item( i ), RETURN_TYPE );
            ret[i] = retType;
        }
        return Arrays.asList( ret );
    }

    /**
     * Returns a map of method names to their return types.
     */
    public HashMap<String, String> getMethodNameToReturnTypeMap() {
        HashMap<String, String> ret = new HashMap<String, String>();
        List<String> methodNames = getAllMethodNames();
        List<String> methodReturnTypes = getAllReturnTypes();
        for( int i = 0; i < methodNames.size(); i++ ) {
            ret.put( methodNames.get( i ), methodReturnTypes.get( i ) );
        }
        return ret;
    }

    /**
     * Accepts a 'Param' Node, which will then parse out the <paramValue> of this node.
     * @param paramNode The param Node, which contains paramType and paramValue.
     * @return The String that exists within the paramValue Element, or an empty string if there doesn't exist any
     * param value.
     */
    public String getParamValue( Node paramNode ) {
        if( paramNode.getNodeType() == Node.ELEMENT_NODE ) {
            //Gets the <paramValue> Node
            Node paramValueNode = ((Element) paramNode).getElementsByTagName( PARAM_VALUE ).item( 0 );
            //Get the children nodes of the <paramValue> node
            NodeList paramValueNodeList = paramValueNode.getChildNodes();
            //Return the value of the first child (will be #text)
            return paramValueNodeList.item( 0 ).getNodeValue();
        }
        return "";
    }

    /**
     * Accepts a 'Method' Node, and returns a list of all Param nodes.
     * @param methodNode The method node we want to get all parameter type/values for.
     * @return A list of all Param Nodes, or null if there are none.
     */
    public NodeList getAllParams( Node methodNode ) {
        if( methodNode.getNodeType() == Node.ELEMENT_NODE ) {
            return ((Element) methodNode).getElementsByTagName( PARAM );
        }
        return null;
    }

    /**
     * Gets the param name attribute of a 'Param' node.
     */
    public String getParamName( Node param ) {
        return ((Element) param).getAttributeNode( PARAM_NAME ).getValue();
    }

    /**
     * Returns a value of an element within a 'Method' Node. For example:
     * @param methodElement The 'Method' Element we are looking at.
     * @param tagname The element's tag name we want to get the value for.
     * @return The String value of the text within the element.
     * For example:
     * <Method>
     *  <returnType>example</returnType>
     * </Method>
     * If we wanted to get "example", we would call getValueOfElementInMethod with the correct
     * 'Method' Node, and "returnType" as the argument to tagname.
     */
    private String getValueOfElementInMethod( Element methodElement, String tagname ) {
        //Get the <methodName> Element
        Element methodNameElement = (Element) methodElement.getElementsByTagName( tagname ).item( 0 );
        //Get the children nodes of the <methodName> element
        NodeList methodNameNodeList = methodNameElement.getChildNodes();
        //Return the value of the first child (will be #text)
        return ((Node) methodNameNodeList.item( 0 )).getNodeValue();
    }
}
